using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Collections.Specialized;
using System.Collections;

namespace GPSCore.NMEA.COMSource
{
    /// <summary>
    /// The Delegate for the event OnGPSFoundOnComPort
    /// </summary>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public delegate void GPSFoundOnComPortEventHandler( COMConfiguration configuration );

    /// <summary>
    /// The Delegate for the event OnGPSNotFoundOnComPort
    /// </summary>
    public delegate void GPSNotFoundOnComPortEventHandler();

    /// <summary>
    /// GPS Com Port Auto Locator
    /// 
    /// This class is an utility class in order to autolocate the GPS on COM Ports
    /// </summary>
    public class COMLocator : IDisposable 
    {
        #region members

        private System.IO.Ports.SerialPort m_serialPort;
        
        private ThreadStart m_founderThreadStart;
        private Thread m_founderThread;

        COMConfiguration _comConfiguration;

        #endregion

        #region constructors

        /// <summary>
        /// GPS Com Port Auto Locator 
        /// </summary>
        public COMLocator()
        {
            _comConfiguration = new COMConfiguration();

            //tipical configuration
            _comConfiguration.DataBits = 8;
            _comConfiguration.Parity = System.IO.Ports.Parity.None;
            _comConfiguration.StopBits = System.IO.Ports.StopBits.One;

            m_founderThreadStart = new ThreadStart(startLocator);
        } 

        #endregion

        #region events

        /// <summary>
        /// Fired when a GPS is founded on a COM Port
        /// </summary>
        public event GPSFoundOnComPortEventHandler OnGPSFoundOnComPort;

        /// <summary>
        /// Fired when a GPS is NOT founded on a COM Port
        /// </summary>
        public event GPSNotFoundOnComPortEventHandler OnGPSNotFoundOnComPort;

        #endregion

        #region methods

        /// <summary>
        /// Start the thread that Search of the COM Port.
        /// 
        /// Wait for the OnGPSFoundOnComPort or OnGPSNotFoundOnComPort events
        /// </summary>
        public void Start()
        {
            if (m_founderThread != null)
            {
                try
                {
                    m_founderThread.Abort();
                }
                catch (Exception ex)
                {
                    string exMess = ex.Message;
                }
            }

            //simply start the thread that search for 
            m_founderThread = new Thread(m_founderThreadStart);
            m_founderThread.Name = "GPSSerialPortLocator";
            m_founderThread.Start();            
        }


        /// <summary>
        /// Thread that explore the com ports 
        /// </summary>
        private void startLocator()
        {
            System.Collections.Generic.List<String> _comPortsAvailable = new List<string>();
            _comPortsAvailable.AddRange(  System.IO.Ports.SerialPort.GetPortNames() );
            
            m_serialPort = new System.IO.Ports.SerialPort();

            List<int> _speedToTest = new List<int>();
            //start looping on this velocity: 4800 bps
            //then 9600 then 14400

            _speedToTest.Add(4800);
            _speedToTest.Add(9600);
            _speedToTest.Add(14400);

            //navigate on evary port available until there is no data founded....

            bool founded = false;

            foreach (int portBaud in _speedToTest)
            {
                if (founded)
                    break;

                _comConfiguration.BaudRate = portBaud;

                foreach (string portName in _comPortsAvailable)
                {
                    lock (this)
                    {
                        _comConfiguration.PortName = portName;
                        COMConfiguration.ConfigureSerialPort(_comConfiguration, m_serialPort);
                    }

                    m_serialPort.ReadBufferSize = 2048; //read simply 2048 byte

                    try
                    {
                        m_serialPort.ReadTimeout = 2000;

                        m_serialPort.Open();

                        Thread.Sleep(1000);
                    }
                    catch(Exception ex)
                    {
                        string exMess = ex.Message;
                    }                    

                    string readedLine = String.Empty;

                    if (m_serialPort.IsOpen)
                    {

                        try
                        {
                            m_serialPort.ReadTimeout = 2000;

                            readedLine = m_serialPort.ReadExisting();

                            if (readedLine.IndexOf("$GP") >= 0)
                            {
                                //ok GPS found! !! FIRE EVENT

                                if (OnGPSFoundOnComPort != null)
                                    OnGPSFoundOnComPort.Invoke(_comConfiguration);

                                founded = true;
                                break;
                            }
                            else
                            {

                            }
                        }
                        catch (TimeoutException ex)
                        {
                            //time out!
                            continue;
                        }
                        finally
                        {
                            if (m_serialPort.IsOpen)
                                m_serialPort.Close();
                        }

                    }
                }
            }

            if (!founded)
            {
                if (OnGPSNotFoundOnComPort != null)
                    OnGPSNotFoundOnComPort.Invoke();            
            }

            if (m_serialPort.IsOpen)
                m_serialPort.Close();

            m_serialPort.Dispose();
            m_serialPort = null;
        } 

        #endregion


        #region IDisposable Members

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            if (m_founderThread != null)
            {
                    m_founderThread.Abort();
                    m_founderThread.Join(2000);
            }

            if (m_serialPort.IsOpen)
                m_serialPort.Close();

            m_serialPort.Dispose();
            m_serialPort = null;
        }

        #endregion
    }
}
